xref: /openbmc/bmcweb/http/http_response.hpp (revision faf100f963c9cd8c81c277ad6bc188eecd0fc12b)
104e438cbSEd Tanous #pragma once
204e438cbSEd Tanous #include "logging.hpp"
33ccb3adbSEd Tanous #include "utils/hex_utils.hpp"
404e438cbSEd Tanous 
504e438cbSEd Tanous #include <boost/beast/http/message.hpp>
6d4b6c660SEd Tanous #include <boost/beast/http/string_body.hpp>
7*faf100f9SEd Tanous #include <nlohmann/json.hpp>
804e438cbSEd Tanous 
98a9a25c8SEd Tanous #include <optional>
1004e438cbSEd Tanous #include <string>
118a9a25c8SEd Tanous #include <string_view>
1204e438cbSEd Tanous 
1304e438cbSEd Tanous namespace crow
1404e438cbSEd Tanous {
1504e438cbSEd Tanous 
1604e438cbSEd Tanous template <typename Adaptor, typename Handler>
1704e438cbSEd Tanous class Connection;
1804e438cbSEd Tanous 
1904e438cbSEd Tanous struct Response
2004e438cbSEd Tanous {
2104e438cbSEd Tanous     template <typename Adaptor, typename Handler>
2204e438cbSEd Tanous     friend class crow::Connection;
2304e438cbSEd Tanous     using response_type =
2404e438cbSEd Tanous         boost::beast::http::response<boost::beast::http::string_body>;
2504e438cbSEd Tanous 
2604e438cbSEd Tanous     std::optional<response_type> stringResponse;
2704e438cbSEd Tanous 
2804e438cbSEd Tanous     nlohmann::json jsonValue;
2904e438cbSEd Tanous 
3026ccae32SEd Tanous     void addHeader(std::string_view key, std::string_view value)
3104e438cbSEd Tanous     {
3204e438cbSEd Tanous         stringResponse->set(key, value);
3304e438cbSEd Tanous     }
3404e438cbSEd Tanous 
3504e438cbSEd Tanous     void addHeader(boost::beast::http::field key, std::string_view value)
3604e438cbSEd Tanous     {
3704e438cbSEd Tanous         stringResponse->set(key, value);
3804e438cbSEd Tanous     }
3904e438cbSEd Tanous 
4089492a15SPatrick Williams     Response() : stringResponse(response_type{}) {}
4104e438cbSEd Tanous 
4213548d85SEd Tanous     Response(Response&& res) noexcept :
43f8fe53e7SEd Tanous         stringResponse(std::move(res.stringResponse)),
44f8fe53e7SEd Tanous         jsonValue(std::move(res.jsonValue)), completed(res.completed)
4513548d85SEd Tanous     {
4613548d85SEd Tanous         // See note in operator= move handler for why this is needed.
4713548d85SEd Tanous         if (!res.completed)
4813548d85SEd Tanous         {
4913548d85SEd Tanous             completeRequestHandler = std::move(res.completeRequestHandler);
5013548d85SEd Tanous             res.completeRequestHandler = nullptr;
5113548d85SEd Tanous         }
5213548d85SEd Tanous         isAliveHelper = res.isAliveHelper;
5313548d85SEd Tanous         res.isAliveHelper = nullptr;
5413548d85SEd Tanous     }
5513548d85SEd Tanous 
56ecd6a3a2SEd Tanous     ~Response() = default;
57ecd6a3a2SEd Tanous 
58ecd6a3a2SEd Tanous     Response(const Response&) = delete;
5972374eb7SNan Zhou 
6004e438cbSEd Tanous     Response& operator=(const Response& r) = delete;
6104e438cbSEd Tanous 
6204e438cbSEd Tanous     Response& operator=(Response&& r) noexcept
6304e438cbSEd Tanous     {
6472374eb7SNan Zhou         BMCWEB_LOG_DEBUG << "Moving response containers; this: " << this
6572374eb7SNan Zhou                          << "; other: " << &r;
6672374eb7SNan Zhou         if (this == &r)
6772374eb7SNan Zhou         {
6872374eb7SNan Zhou             return *this;
6972374eb7SNan Zhou         }
7004e438cbSEd Tanous         stringResponse = std::move(r.stringResponse);
7104e438cbSEd Tanous         r.stringResponse.emplace(response_type{});
7204e438cbSEd Tanous         jsonValue = std::move(r.jsonValue);
7313548d85SEd Tanous 
7413548d85SEd Tanous         // Only need to move completion handler if not already completed
7513548d85SEd Tanous         // Note, there are cases where we might move out of a Response object
7613548d85SEd Tanous         // while in a completion handler for that response object.  This check
7713548d85SEd Tanous         // is intended to prevent destructing the functor we are currently
7813548d85SEd Tanous         // executing from in that case.
7913548d85SEd Tanous         if (!r.completed)
8013548d85SEd Tanous         {
8172374eb7SNan Zhou             completeRequestHandler = std::move(r.completeRequestHandler);
8272374eb7SNan Zhou             r.completeRequestHandler = nullptr;
8313548d85SEd Tanous         }
8413548d85SEd Tanous         else
8513548d85SEd Tanous         {
8613548d85SEd Tanous             completeRequestHandler = nullptr;
8713548d85SEd Tanous         }
8813548d85SEd Tanous         completed = r.completed;
8913548d85SEd Tanous         isAliveHelper = std::move(r.isAliveHelper);
9072374eb7SNan Zhou         r.isAliveHelper = nullptr;
9104e438cbSEd Tanous         return *this;
9204e438cbSEd Tanous     }
9304e438cbSEd Tanous 
943590bd1dSNan Zhou     void result(unsigned v)
953590bd1dSNan Zhou     {
963590bd1dSNan Zhou         stringResponse->result(v);
973590bd1dSNan Zhou     }
983590bd1dSNan Zhou 
9904e438cbSEd Tanous     void result(boost::beast::http::status v)
10004e438cbSEd Tanous     {
10104e438cbSEd Tanous         stringResponse->result(v);
10204e438cbSEd Tanous     }
10304e438cbSEd Tanous 
104bb60f4deSEd Tanous     boost::beast::http::status result() const
10504e438cbSEd Tanous     {
10604e438cbSEd Tanous         return stringResponse->result();
10704e438cbSEd Tanous     }
10804e438cbSEd Tanous 
109039a47e3SCarson Labrado     unsigned resultInt() const
11004e438cbSEd Tanous     {
11104e438cbSEd Tanous         return stringResponse->result_int();
11204e438cbSEd Tanous     }
11304e438cbSEd Tanous 
114bb60f4deSEd Tanous     std::string_view reason() const
11504e438cbSEd Tanous     {
11604e438cbSEd Tanous         return stringResponse->reason();
11704e438cbSEd Tanous     }
11804e438cbSEd Tanous 
11904e438cbSEd Tanous     bool isCompleted() const noexcept
12004e438cbSEd Tanous     {
12104e438cbSEd Tanous         return completed;
12204e438cbSEd Tanous     }
12304e438cbSEd Tanous 
12404e438cbSEd Tanous     std::string& body()
12504e438cbSEd Tanous     {
12604e438cbSEd Tanous         return stringResponse->body();
12704e438cbSEd Tanous     }
12804e438cbSEd Tanous 
12946a81465SCarson Labrado     std::string_view getHeaderValue(std::string_view key) const
13046a81465SCarson Labrado     {
13146a81465SCarson Labrado         return stringResponse->base()[key];
13246a81465SCarson Labrado     }
13346a81465SCarson Labrado 
13404e438cbSEd Tanous     void keepAlive(bool k)
13504e438cbSEd Tanous     {
13604e438cbSEd Tanous         stringResponse->keep_alive(k);
13704e438cbSEd Tanous     }
13804e438cbSEd Tanous 
139bb60f4deSEd Tanous     bool keepAlive() const
14004e438cbSEd Tanous     {
14104e438cbSEd Tanous         return stringResponse->keep_alive();
14204e438cbSEd Tanous     }
14304e438cbSEd Tanous 
14404e438cbSEd Tanous     void preparePayload()
14504e438cbSEd Tanous     {
146eea9c979SEd Tanous         // This code is a throw-free equivalent to
147eea9c979SEd Tanous         // beast::http::message::prepare_payload
148eea9c979SEd Tanous         boost::optional<uint64_t> pSize = stringResponse->payload_size();
149eea9c979SEd Tanous         using boost::beast::http::status;
150eea9c979SEd Tanous         using boost::beast::http::status_class;
151eea9c979SEd Tanous         using boost::beast::http::to_status_class;
152eea9c979SEd Tanous         if (!pSize)
153eea9c979SEd Tanous         {
154eea9c979SEd Tanous             pSize = 0;
155eea9c979SEd Tanous         }
156eea9c979SEd Tanous         else
157eea9c979SEd Tanous         {
158eea9c979SEd Tanous             bool is1XXReturn = to_status_class(stringResponse->result()) ==
159eea9c979SEd Tanous                                status_class::informational;
160eea9c979SEd Tanous             if (*pSize > 0 &&
161eea9c979SEd Tanous                 (is1XXReturn ||
162eea9c979SEd Tanous                  stringResponse->result() == status::no_content ||
163eea9c979SEd Tanous                  stringResponse->result() == status::not_modified))
164eea9c979SEd Tanous             {
165eea9c979SEd Tanous                 BMCWEB_LOG_CRITICAL
166eea9c979SEd Tanous                     << this
167eea9c979SEd Tanous                     << " Response content provided but code was no-content or not_modified, which aren't allowed to have a body";
168eea9c979SEd Tanous                 pSize = 0;
169eea9c979SEd Tanous                 body().clear();
170eea9c979SEd Tanous             }
171eea9c979SEd Tanous         }
172eea9c979SEd Tanous         stringResponse->content_length(*pSize);
17304e438cbSEd Tanous     }
17404e438cbSEd Tanous 
17504e438cbSEd Tanous     void clear()
17604e438cbSEd Tanous     {
17704e438cbSEd Tanous         BMCWEB_LOG_DEBUG << this << " Clearing response containers";
17804e438cbSEd Tanous         stringResponse.emplace(response_type{});
179a6695a84SEd Tanous         jsonValue = nullptr;
18004e438cbSEd Tanous         completed = false;
181291d709dSEd Tanous         expectedHash = std::nullopt;
18204e438cbSEd Tanous     }
18304e438cbSEd Tanous 
18481ce609eSEd Tanous     void write(std::string_view bodyPart)
18504e438cbSEd Tanous     {
18681ce609eSEd Tanous         stringResponse->body() += std::string(bodyPart);
18704e438cbSEd Tanous     }
18804e438cbSEd Tanous 
1892d6cb56bSEd Tanous     std::string computeEtag() const
19004e438cbSEd Tanous     {
19189f18008SEd Tanous         // Only set etag if this request succeeded
1922d6cb56bSEd Tanous         if (result() != boost::beast::http::status::ok)
19389f18008SEd Tanous         {
1942d6cb56bSEd Tanous             return "";
19589f18008SEd Tanous         }
1962d6cb56bSEd Tanous         // and the json response isn't empty
1972d6cb56bSEd Tanous         if (jsonValue.empty())
1982d6cb56bSEd Tanous         {
1992d6cb56bSEd Tanous             return "";
2002d6cb56bSEd Tanous         }
2012d6cb56bSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
2022d6cb56bSEd Tanous         return "\"" + intToHexString(hashval, 8) + "\"";
2032d6cb56bSEd Tanous     }
2042d6cb56bSEd Tanous 
2052d6cb56bSEd Tanous     void end()
2062d6cb56bSEd Tanous     {
2072d6cb56bSEd Tanous         std::string etag = computeEtag();
2082d6cb56bSEd Tanous         if (!etag.empty())
2092d6cb56bSEd Tanous         {
2102d6cb56bSEd Tanous             addHeader(boost::beast::http::field::etag, etag);
21189f18008SEd Tanous         }
21204e438cbSEd Tanous         if (completed)
21304e438cbSEd Tanous         {
21472374eb7SNan Zhou             BMCWEB_LOG_ERROR << this << " Response was ended twice";
21504e438cbSEd Tanous             return;
21604e438cbSEd Tanous         }
21704e438cbSEd Tanous         completed = true;
21872374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " calling completion handler";
21904e438cbSEd Tanous         if (completeRequestHandler)
22004e438cbSEd Tanous         {
22172374eb7SNan Zhou             BMCWEB_LOG_DEBUG << this << " completion handler was valid";
22272374eb7SNan Zhou             completeRequestHandler(*this);
22304e438cbSEd Tanous         }
22404e438cbSEd Tanous     }
22504e438cbSEd Tanous 
226bb60f4deSEd Tanous     bool isAlive() const
22704e438cbSEd Tanous     {
22804e438cbSEd Tanous         return isAliveHelper && isAliveHelper();
22904e438cbSEd Tanous     }
23004e438cbSEd Tanous 
23172374eb7SNan Zhou     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
2324147b8acSJohn Edward Broadbent     {
23372374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " setting completion handler";
23472374eb7SNan Zhou         completeRequestHandler = std::move(handler);
23513548d85SEd Tanous 
23613548d85SEd Tanous         // Now that we have a new completion handler attached, we're no longer
23713548d85SEd Tanous         // complete
23813548d85SEd Tanous         completed = false;
23972374eb7SNan Zhou     }
24072374eb7SNan Zhou 
24172374eb7SNan Zhou     std::function<void(Response&)> releaseCompleteRequestHandler()
24272374eb7SNan Zhou     {
24372374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " releasing completion handler"
24472374eb7SNan Zhou                          << static_cast<bool>(completeRequestHandler);
24572374eb7SNan Zhou         std::function<void(Response&)> ret = completeRequestHandler;
24672374eb7SNan Zhou         completeRequestHandler = nullptr;
24713548d85SEd Tanous         completed = true;
24872374eb7SNan Zhou         return ret;
24972374eb7SNan Zhou     }
25072374eb7SNan Zhou 
25172374eb7SNan Zhou     void setIsAliveHelper(std::function<bool()>&& handler)
25272374eb7SNan Zhou     {
25372374eb7SNan Zhou         isAliveHelper = std::move(handler);
25472374eb7SNan Zhou     }
25572374eb7SNan Zhou 
25672374eb7SNan Zhou     std::function<bool()> releaseIsAliveHelper()
25772374eb7SNan Zhou     {
25872374eb7SNan Zhou         std::function<bool()> ret = std::move(isAliveHelper);
25972374eb7SNan Zhou         isAliveHelper = nullptr;
26072374eb7SNan Zhou         return ret;
2614147b8acSJohn Edward Broadbent     }
2624147b8acSJohn Edward Broadbent 
263291d709dSEd Tanous     void setHashAndHandleNotModified()
264291d709dSEd Tanous     {
265291d709dSEd Tanous         // Can only hash if we have content that's valid
266291d709dSEd Tanous         if (jsonValue.empty() || result() != boost::beast::http::status::ok)
267291d709dSEd Tanous         {
268291d709dSEd Tanous             return;
269291d709dSEd Tanous         }
270291d709dSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
271291d709dSEd Tanous         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
272291d709dSEd Tanous         addHeader(boost::beast::http::field::etag, hexVal);
273291d709dSEd Tanous         if (expectedHash && hexVal == *expectedHash)
274291d709dSEd Tanous         {
275a6695a84SEd Tanous             jsonValue = nullptr;
276291d709dSEd Tanous             result(boost::beast::http::status::not_modified);
277291d709dSEd Tanous         }
278291d709dSEd Tanous     }
279291d709dSEd Tanous 
280291d709dSEd Tanous     void setExpectedHash(std::string_view hash)
281291d709dSEd Tanous     {
282291d709dSEd Tanous         expectedHash = hash;
283291d709dSEd Tanous     }
284291d709dSEd Tanous 
28504e438cbSEd Tanous   private:
286291d709dSEd Tanous     std::optional<std::string> expectedHash;
28772374eb7SNan Zhou     bool completed = false;
28872374eb7SNan Zhou     std::function<void(Response&)> completeRequestHandler;
28904e438cbSEd Tanous     std::function<bool()> isAliveHelper;
29004e438cbSEd Tanous };
29104e438cbSEd Tanous } // namespace crow
292