xref: /openbmc/bmcweb/http/http_response.hpp (revision 994fd86a3f6649a820f66313765e85e762ad105a)
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>
7faf100f9SEd 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     {
32*994fd86aSEd Tanous         stringResponse->insert(key, value);
3304e438cbSEd Tanous     }
3404e438cbSEd Tanous 
3504e438cbSEd Tanous     void addHeader(boost::beast::http::field key, std::string_view value)
3604e438cbSEd Tanous     {
37*994fd86aSEd Tanous         stringResponse->insert(key, value);
38*994fd86aSEd Tanous     }
39*994fd86aSEd Tanous 
40*994fd86aSEd Tanous     void clearHeader(boost::beast::http::field key)
41*994fd86aSEd Tanous     {
42*994fd86aSEd Tanous         stringResponse->erase(key);
4304e438cbSEd Tanous     }
4404e438cbSEd Tanous 
4589492a15SPatrick Williams     Response() : stringResponse(response_type{}) {}
4604e438cbSEd Tanous 
4713548d85SEd Tanous     Response(Response&& res) noexcept :
48f8fe53e7SEd Tanous         stringResponse(std::move(res.stringResponse)),
49f8fe53e7SEd Tanous         jsonValue(std::move(res.jsonValue)), completed(res.completed)
5013548d85SEd Tanous     {
5113548d85SEd Tanous         // See note in operator= move handler for why this is needed.
5213548d85SEd Tanous         if (!res.completed)
5313548d85SEd Tanous         {
5413548d85SEd Tanous             completeRequestHandler = std::move(res.completeRequestHandler);
5513548d85SEd Tanous             res.completeRequestHandler = nullptr;
5613548d85SEd Tanous         }
5713548d85SEd Tanous         isAliveHelper = res.isAliveHelper;
5813548d85SEd Tanous         res.isAliveHelper = nullptr;
5913548d85SEd Tanous     }
6013548d85SEd Tanous 
61ecd6a3a2SEd Tanous     ~Response() = default;
62ecd6a3a2SEd Tanous 
63ecd6a3a2SEd Tanous     Response(const Response&) = delete;
6472374eb7SNan Zhou 
6504e438cbSEd Tanous     Response& operator=(const Response& r) = delete;
6604e438cbSEd Tanous 
6704e438cbSEd Tanous     Response& operator=(Response&& r) noexcept
6804e438cbSEd Tanous     {
6972374eb7SNan Zhou         BMCWEB_LOG_DEBUG << "Moving response containers; this: " << this
7072374eb7SNan Zhou                          << "; other: " << &r;
7172374eb7SNan Zhou         if (this == &r)
7272374eb7SNan Zhou         {
7372374eb7SNan Zhou             return *this;
7472374eb7SNan Zhou         }
7504e438cbSEd Tanous         stringResponse = std::move(r.stringResponse);
7604e438cbSEd Tanous         r.stringResponse.emplace(response_type{});
7704e438cbSEd Tanous         jsonValue = std::move(r.jsonValue);
7813548d85SEd Tanous 
7913548d85SEd Tanous         // Only need to move completion handler if not already completed
8013548d85SEd Tanous         // Note, there are cases where we might move out of a Response object
8113548d85SEd Tanous         // while in a completion handler for that response object.  This check
8213548d85SEd Tanous         // is intended to prevent destructing the functor we are currently
8313548d85SEd Tanous         // executing from in that case.
8413548d85SEd Tanous         if (!r.completed)
8513548d85SEd Tanous         {
8672374eb7SNan Zhou             completeRequestHandler = std::move(r.completeRequestHandler);
8772374eb7SNan Zhou             r.completeRequestHandler = nullptr;
8813548d85SEd Tanous         }
8913548d85SEd Tanous         else
9013548d85SEd Tanous         {
9113548d85SEd Tanous             completeRequestHandler = nullptr;
9213548d85SEd Tanous         }
9313548d85SEd Tanous         completed = r.completed;
9413548d85SEd Tanous         isAliveHelper = std::move(r.isAliveHelper);
9572374eb7SNan Zhou         r.isAliveHelper = nullptr;
9604e438cbSEd Tanous         return *this;
9704e438cbSEd Tanous     }
9804e438cbSEd Tanous 
993590bd1dSNan Zhou     void result(unsigned v)
1003590bd1dSNan Zhou     {
1013590bd1dSNan Zhou         stringResponse->result(v);
1023590bd1dSNan Zhou     }
1033590bd1dSNan Zhou 
10404e438cbSEd Tanous     void result(boost::beast::http::status v)
10504e438cbSEd Tanous     {
10604e438cbSEd Tanous         stringResponse->result(v);
10704e438cbSEd Tanous     }
10804e438cbSEd Tanous 
109bb60f4deSEd Tanous     boost::beast::http::status result() const
11004e438cbSEd Tanous     {
11104e438cbSEd Tanous         return stringResponse->result();
11204e438cbSEd Tanous     }
11304e438cbSEd Tanous 
114039a47e3SCarson Labrado     unsigned resultInt() const
11504e438cbSEd Tanous     {
11604e438cbSEd Tanous         return stringResponse->result_int();
11704e438cbSEd Tanous     }
11804e438cbSEd Tanous 
119bb60f4deSEd Tanous     std::string_view reason() const
12004e438cbSEd Tanous     {
12104e438cbSEd Tanous         return stringResponse->reason();
12204e438cbSEd Tanous     }
12304e438cbSEd Tanous 
12404e438cbSEd Tanous     bool isCompleted() const noexcept
12504e438cbSEd Tanous     {
12604e438cbSEd Tanous         return completed;
12704e438cbSEd Tanous     }
12804e438cbSEd Tanous 
12904e438cbSEd Tanous     std::string& body()
13004e438cbSEd Tanous     {
13104e438cbSEd Tanous         return stringResponse->body();
13204e438cbSEd Tanous     }
13304e438cbSEd Tanous 
13446a81465SCarson Labrado     std::string_view getHeaderValue(std::string_view key) const
13546a81465SCarson Labrado     {
13646a81465SCarson Labrado         return stringResponse->base()[key];
13746a81465SCarson Labrado     }
13846a81465SCarson Labrado 
13904e438cbSEd Tanous     void keepAlive(bool k)
14004e438cbSEd Tanous     {
14104e438cbSEd Tanous         stringResponse->keep_alive(k);
14204e438cbSEd Tanous     }
14304e438cbSEd Tanous 
144bb60f4deSEd Tanous     bool keepAlive() const
14504e438cbSEd Tanous     {
14604e438cbSEd Tanous         return stringResponse->keep_alive();
14704e438cbSEd Tanous     }
14804e438cbSEd Tanous 
14904e438cbSEd Tanous     void preparePayload()
15004e438cbSEd Tanous     {
151eea9c979SEd Tanous         // This code is a throw-free equivalent to
152eea9c979SEd Tanous         // beast::http::message::prepare_payload
153eea9c979SEd Tanous         boost::optional<uint64_t> pSize = stringResponse->payload_size();
154eea9c979SEd Tanous         using boost::beast::http::status;
155eea9c979SEd Tanous         using boost::beast::http::status_class;
156eea9c979SEd Tanous         using boost::beast::http::to_status_class;
157eea9c979SEd Tanous         if (!pSize)
158eea9c979SEd Tanous         {
159eea9c979SEd Tanous             pSize = 0;
160eea9c979SEd Tanous         }
161eea9c979SEd Tanous         else
162eea9c979SEd Tanous         {
163eea9c979SEd Tanous             bool is1XXReturn = to_status_class(stringResponse->result()) ==
164eea9c979SEd Tanous                                status_class::informational;
165eea9c979SEd Tanous             if (*pSize > 0 &&
166eea9c979SEd Tanous                 (is1XXReturn ||
167eea9c979SEd Tanous                  stringResponse->result() == status::no_content ||
168eea9c979SEd Tanous                  stringResponse->result() == status::not_modified))
169eea9c979SEd Tanous             {
170eea9c979SEd Tanous                 BMCWEB_LOG_CRITICAL
171eea9c979SEd Tanous                     << this
172eea9c979SEd Tanous                     << " Response content provided but code was no-content or not_modified, which aren't allowed to have a body";
173eea9c979SEd Tanous                 pSize = 0;
174eea9c979SEd Tanous                 body().clear();
175eea9c979SEd Tanous             }
176eea9c979SEd Tanous         }
177eea9c979SEd Tanous         stringResponse->content_length(*pSize);
17804e438cbSEd Tanous     }
17904e438cbSEd Tanous 
18004e438cbSEd Tanous     void clear()
18104e438cbSEd Tanous     {
18204e438cbSEd Tanous         BMCWEB_LOG_DEBUG << this << " Clearing response containers";
18304e438cbSEd Tanous         stringResponse.emplace(response_type{});
184a6695a84SEd Tanous         jsonValue = nullptr;
18504e438cbSEd Tanous         completed = false;
186291d709dSEd Tanous         expectedHash = std::nullopt;
18704e438cbSEd Tanous     }
18804e438cbSEd Tanous 
18981ce609eSEd Tanous     void write(std::string_view bodyPart)
19004e438cbSEd Tanous     {
19181ce609eSEd Tanous         stringResponse->body() += std::string(bodyPart);
19204e438cbSEd Tanous     }
19304e438cbSEd Tanous 
1942d6cb56bSEd Tanous     std::string computeEtag() const
19504e438cbSEd Tanous     {
19689f18008SEd Tanous         // Only set etag if this request succeeded
1972d6cb56bSEd Tanous         if (result() != boost::beast::http::status::ok)
19889f18008SEd Tanous         {
1992d6cb56bSEd Tanous             return "";
20089f18008SEd Tanous         }
2012d6cb56bSEd Tanous         // and the json response isn't empty
2022d6cb56bSEd Tanous         if (jsonValue.empty())
2032d6cb56bSEd Tanous         {
2042d6cb56bSEd Tanous             return "";
2052d6cb56bSEd Tanous         }
2062d6cb56bSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
2072d6cb56bSEd Tanous         return "\"" + intToHexString(hashval, 8) + "\"";
2082d6cb56bSEd Tanous     }
2092d6cb56bSEd Tanous 
2102d6cb56bSEd Tanous     void end()
2112d6cb56bSEd Tanous     {
2122d6cb56bSEd Tanous         std::string etag = computeEtag();
2132d6cb56bSEd Tanous         if (!etag.empty())
2142d6cb56bSEd Tanous         {
2152d6cb56bSEd Tanous             addHeader(boost::beast::http::field::etag, etag);
21689f18008SEd Tanous         }
21704e438cbSEd Tanous         if (completed)
21804e438cbSEd Tanous         {
21972374eb7SNan Zhou             BMCWEB_LOG_ERROR << this << " Response was ended twice";
22004e438cbSEd Tanous             return;
22104e438cbSEd Tanous         }
22204e438cbSEd Tanous         completed = true;
22372374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " calling completion handler";
22404e438cbSEd Tanous         if (completeRequestHandler)
22504e438cbSEd Tanous         {
22672374eb7SNan Zhou             BMCWEB_LOG_DEBUG << this << " completion handler was valid";
22772374eb7SNan Zhou             completeRequestHandler(*this);
22804e438cbSEd Tanous         }
22904e438cbSEd Tanous     }
23004e438cbSEd Tanous 
231bb60f4deSEd Tanous     bool isAlive() const
23204e438cbSEd Tanous     {
23304e438cbSEd Tanous         return isAliveHelper && isAliveHelper();
23404e438cbSEd Tanous     }
23504e438cbSEd Tanous 
23672374eb7SNan Zhou     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
2374147b8acSJohn Edward Broadbent     {
23872374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " setting completion handler";
23972374eb7SNan Zhou         completeRequestHandler = std::move(handler);
24013548d85SEd Tanous 
24113548d85SEd Tanous         // Now that we have a new completion handler attached, we're no longer
24213548d85SEd Tanous         // complete
24313548d85SEd Tanous         completed = false;
24472374eb7SNan Zhou     }
24572374eb7SNan Zhou 
24672374eb7SNan Zhou     std::function<void(Response&)> releaseCompleteRequestHandler()
24772374eb7SNan Zhou     {
24872374eb7SNan Zhou         BMCWEB_LOG_DEBUG << this << " releasing completion handler"
24972374eb7SNan Zhou                          << static_cast<bool>(completeRequestHandler);
25072374eb7SNan Zhou         std::function<void(Response&)> ret = completeRequestHandler;
25172374eb7SNan Zhou         completeRequestHandler = nullptr;
25213548d85SEd Tanous         completed = true;
25372374eb7SNan Zhou         return ret;
25472374eb7SNan Zhou     }
25572374eb7SNan Zhou 
25672374eb7SNan Zhou     void setIsAliveHelper(std::function<bool()>&& handler)
25772374eb7SNan Zhou     {
25872374eb7SNan Zhou         isAliveHelper = std::move(handler);
25972374eb7SNan Zhou     }
26072374eb7SNan Zhou 
26172374eb7SNan Zhou     std::function<bool()> releaseIsAliveHelper()
26272374eb7SNan Zhou     {
26372374eb7SNan Zhou         std::function<bool()> ret = std::move(isAliveHelper);
26472374eb7SNan Zhou         isAliveHelper = nullptr;
26572374eb7SNan Zhou         return ret;
2664147b8acSJohn Edward Broadbent     }
2674147b8acSJohn Edward Broadbent 
268291d709dSEd Tanous     void setHashAndHandleNotModified()
269291d709dSEd Tanous     {
270291d709dSEd Tanous         // Can only hash if we have content that's valid
271291d709dSEd Tanous         if (jsonValue.empty() || result() != boost::beast::http::status::ok)
272291d709dSEd Tanous         {
273291d709dSEd Tanous             return;
274291d709dSEd Tanous         }
275291d709dSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
276291d709dSEd Tanous         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
277291d709dSEd Tanous         addHeader(boost::beast::http::field::etag, hexVal);
278291d709dSEd Tanous         if (expectedHash && hexVal == *expectedHash)
279291d709dSEd Tanous         {
280a6695a84SEd Tanous             jsonValue = nullptr;
281291d709dSEd Tanous             result(boost::beast::http::status::not_modified);
282291d709dSEd Tanous         }
283291d709dSEd Tanous     }
284291d709dSEd Tanous 
285291d709dSEd Tanous     void setExpectedHash(std::string_view hash)
286291d709dSEd Tanous     {
287291d709dSEd Tanous         expectedHash = hash;
288291d709dSEd Tanous     }
289291d709dSEd Tanous 
29004e438cbSEd Tanous   private:
291291d709dSEd Tanous     std::optional<std::string> expectedHash;
29272374eb7SNan Zhou     bool completed = false;
29372374eb7SNan Zhou     std::function<void(Response&)> completeRequestHandler;
29404e438cbSEd Tanous     std::function<bool()> isAliveHelper;
29504e438cbSEd Tanous };
29604e438cbSEd Tanous } // namespace crow
297