xref: /openbmc/bmcweb/http/http_response.hpp (revision 52e31629978bbcefc0e14c96272ef77c2dad6a9c)
104e438cbSEd Tanous #pragma once
2b5f288d2SAbhilash 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>
7faf100f9SEd Tanous #include <nlohmann/json.hpp>
804e438cbSEd Tanous 
98a9a25c8SEd Tanous #include <optional>
1004e438cbSEd Tanous #include <string>
118a9a25c8SEd Tanous #include <string_view>
128e3f7032SAbhilash Raju #include <utility>
1304e438cbSEd Tanous namespace crow
1404e438cbSEd Tanous {
1504e438cbSEd Tanous 
1604e438cbSEd Tanous template <typename Adaptor, typename Handler>
1704e438cbSEd Tanous class Connection;
1804e438cbSEd Tanous 
1927b0cf90SEd Tanous namespace http = boost::beast::http;
2027b0cf90SEd Tanous 
2104e438cbSEd Tanous struct Response
2204e438cbSEd Tanous {
2304e438cbSEd Tanous     template <typename Adaptor, typename Handler>
2404e438cbSEd Tanous     friend class crow::Connection;
2504e438cbSEd Tanous 
26*52e31629SEd Tanous     http::response<bmcweb::FileBody> response;
2704e438cbSEd Tanous 
2804e438cbSEd Tanous     nlohmann::json jsonValue;
2927b0cf90SEd Tanous     using fields_type = http::header<false, http::fields>;
3027b0cf90SEd Tanous     fields_type& fields()
3127b0cf90SEd Tanous     {
32*52e31629SEd Tanous         return response.base();
3327b0cf90SEd Tanous     }
3427b0cf90SEd Tanous 
3527b0cf90SEd Tanous     const fields_type& fields() const
3627b0cf90SEd Tanous     {
37*52e31629SEd Tanous         return response.base();
3827b0cf90SEd Tanous     }
3904e438cbSEd Tanous 
4026ccae32SEd Tanous     void addHeader(std::string_view key, std::string_view value)
4104e438cbSEd Tanous     {
4227b0cf90SEd Tanous         fields().insert(key, value);
4304e438cbSEd Tanous     }
4404e438cbSEd Tanous 
4527b0cf90SEd Tanous     void addHeader(http::field key, std::string_view value)
4604e438cbSEd Tanous     {
4727b0cf90SEd Tanous         fields().insert(key, value);
48994fd86aSEd Tanous     }
49994fd86aSEd Tanous 
5027b0cf90SEd Tanous     void clearHeader(http::field key)
51994fd86aSEd Tanous     {
5227b0cf90SEd Tanous         fields().erase(key);
5304e438cbSEd Tanous     }
5404e438cbSEd Tanous 
55*52e31629SEd Tanous     Response() = default;
5613548d85SEd Tanous     Response(Response&& res) noexcept :
5727b0cf90SEd Tanous         response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
5827b0cf90SEd Tanous         completed(res.completed)
5913548d85SEd Tanous     {
6013548d85SEd Tanous         // See note in operator= move handler for why this is needed.
6113548d85SEd Tanous         if (!res.completed)
6213548d85SEd Tanous         {
6313548d85SEd Tanous             completeRequestHandler = std::move(res.completeRequestHandler);
6413548d85SEd Tanous             res.completeRequestHandler = nullptr;
6513548d85SEd Tanous         }
6613548d85SEd Tanous     }
6713548d85SEd Tanous 
68ecd6a3a2SEd Tanous     ~Response() = default;
69ecd6a3a2SEd Tanous 
70ecd6a3a2SEd Tanous     Response(const Response&) = delete;
7104e438cbSEd Tanous     Response& operator=(const Response& r) = delete;
7204e438cbSEd Tanous 
7304e438cbSEd Tanous     Response& operator=(Response&& r) noexcept
7404e438cbSEd Tanous     {
7562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
7662598e31SEd Tanous                          logPtr(this), logPtr(&r));
7772374eb7SNan Zhou         if (this == &r)
7872374eb7SNan Zhou         {
7972374eb7SNan Zhou             return *this;
8072374eb7SNan Zhou         }
8127b0cf90SEd Tanous         response = std::move(r.response);
8204e438cbSEd Tanous         jsonValue = std::move(r.jsonValue);
8313548d85SEd Tanous 
8413548d85SEd Tanous         // Only need to move completion handler if not already completed
8513548d85SEd Tanous         // Note, there are cases where we might move out of a Response object
8613548d85SEd Tanous         // while in a completion handler for that response object.  This check
8713548d85SEd Tanous         // is intended to prevent destructing the functor we are currently
8813548d85SEd Tanous         // executing from in that case.
8913548d85SEd Tanous         if (!r.completed)
9013548d85SEd Tanous         {
9172374eb7SNan Zhou             completeRequestHandler = std::move(r.completeRequestHandler);
9272374eb7SNan Zhou             r.completeRequestHandler = nullptr;
9313548d85SEd Tanous         }
9413548d85SEd Tanous         else
9513548d85SEd Tanous         {
9613548d85SEd Tanous             completeRequestHandler = nullptr;
9713548d85SEd Tanous         }
9813548d85SEd Tanous         completed = r.completed;
9904e438cbSEd Tanous         return *this;
10004e438cbSEd Tanous     }
10104e438cbSEd Tanous 
1023590bd1dSNan Zhou     void result(unsigned v)
1033590bd1dSNan Zhou     {
10427b0cf90SEd Tanous         fields().result(v);
1053590bd1dSNan Zhou     }
1063590bd1dSNan Zhou 
10727b0cf90SEd Tanous     void result(http::status v)
10804e438cbSEd Tanous     {
10927b0cf90SEd Tanous         fields().result(v);
11004e438cbSEd Tanous     }
11104e438cbSEd Tanous 
11227b0cf90SEd Tanous     void copyBody(const Response& res)
11304e438cbSEd Tanous     {
114*52e31629SEd Tanous         response.body() = res.response.body();
11527b0cf90SEd Tanous     }
11627b0cf90SEd Tanous 
11727b0cf90SEd Tanous     http::status result() const
11827b0cf90SEd Tanous     {
11927b0cf90SEd Tanous         return fields().result();
12004e438cbSEd Tanous     }
12104e438cbSEd Tanous 
122039a47e3SCarson Labrado     unsigned resultInt() const
12304e438cbSEd Tanous     {
12427b0cf90SEd Tanous         return fields().result_int();
12504e438cbSEd Tanous     }
12604e438cbSEd Tanous 
127bb60f4deSEd Tanous     std::string_view reason() const
12804e438cbSEd Tanous     {
12927b0cf90SEd Tanous         return fields().reason();
13004e438cbSEd Tanous     }
13104e438cbSEd Tanous 
13204e438cbSEd Tanous     bool isCompleted() const noexcept
13304e438cbSEd Tanous     {
13404e438cbSEd Tanous         return completed;
13504e438cbSEd Tanous     }
13604e438cbSEd Tanous 
13727b0cf90SEd Tanous     const std::string* body()
13804e438cbSEd Tanous     {
139*52e31629SEd Tanous         return &response.body().str();
14004e438cbSEd Tanous     }
14104e438cbSEd Tanous 
14246a81465SCarson Labrado     std::string_view getHeaderValue(std::string_view key) const
14346a81465SCarson Labrado     {
14427b0cf90SEd Tanous         return fields()[key];
14546a81465SCarson Labrado     }
14646a81465SCarson Labrado 
14704e438cbSEd Tanous     void keepAlive(bool k)
14804e438cbSEd Tanous     {
149*52e31629SEd Tanous         response.keep_alive(k);
15004e438cbSEd Tanous     }
15104e438cbSEd Tanous 
152bb60f4deSEd Tanous     bool keepAlive() const
15304e438cbSEd Tanous     {
154*52e31629SEd Tanous         return response.keep_alive();
15527b0cf90SEd Tanous     }
15627b0cf90SEd Tanous 
157*52e31629SEd Tanous     std::optional<uint64_t> size()
158*52e31629SEd Tanous     {
159*52e31629SEd Tanous         return response.body().payloadSize();
160*52e31629SEd Tanous     }
161*52e31629SEd Tanous 
162*52e31629SEd Tanous     void preparePayload()
16327b0cf90SEd Tanous     {
16427b0cf90SEd Tanous         // This code is a throw-free equivalent to
16527b0cf90SEd Tanous         // beast::http::message::prepare_payload
166*52e31629SEd Tanous         std::optional<uint64_t> pSize = response.body().payloadSize();
167*52e31629SEd Tanous         if (!pSize)
168*52e31629SEd Tanous         {
169*52e31629SEd Tanous             return;
170*52e31629SEd Tanous         }
17127b0cf90SEd Tanous         using http::status;
17227b0cf90SEd Tanous         using http::status_class;
17327b0cf90SEd Tanous         using http::to_status_class;
17427b0cf90SEd Tanous         bool is1XXReturn = to_status_class(result()) ==
17527b0cf90SEd Tanous                            status_class::informational;
17627b0cf90SEd Tanous         if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
17727b0cf90SEd Tanous                            result() == status::not_modified))
17827b0cf90SEd Tanous         {
17927b0cf90SEd Tanous             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
18027b0cf90SEd Tanous                                 "no-content or not_modified, which aren't "
18127b0cf90SEd Tanous                                 "allowed to have a body",
18227b0cf90SEd Tanous                                 logPtr(this));
183*52e31629SEd Tanous             response.content_length(0);
184*52e31629SEd Tanous             return;
18527b0cf90SEd Tanous         }
186*52e31629SEd Tanous         response.content_length(*pSize);
18704e438cbSEd Tanous     }
18804e438cbSEd Tanous 
18904e438cbSEd Tanous     void clear()
19004e438cbSEd Tanous     {
19162598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
192*52e31629SEd Tanous         response.clear();
193*52e31629SEd Tanous 
194a6695a84SEd Tanous         jsonValue = nullptr;
19504e438cbSEd Tanous         completed = false;
196291d709dSEd Tanous         expectedHash = std::nullopt;
19704e438cbSEd Tanous     }
19804e438cbSEd Tanous 
1992d6cb56bSEd Tanous     std::string computeEtag() const
20004e438cbSEd Tanous     {
20189f18008SEd Tanous         // Only set etag if this request succeeded
20227b0cf90SEd Tanous         if (result() != http::status::ok)
20389f18008SEd Tanous         {
2042d6cb56bSEd Tanous             return "";
20589f18008SEd Tanous         }
2062d6cb56bSEd Tanous         // and the json response isn't empty
2072d6cb56bSEd Tanous         if (jsonValue.empty())
2082d6cb56bSEd Tanous         {
2092d6cb56bSEd Tanous             return "";
2102d6cb56bSEd Tanous         }
2112d6cb56bSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
2122d6cb56bSEd Tanous         return "\"" + intToHexString(hashval, 8) + "\"";
2132d6cb56bSEd Tanous     }
2142d6cb56bSEd Tanous 
21527b0cf90SEd Tanous     void write(std::string&& bodyPart)
21627b0cf90SEd Tanous     {
217*52e31629SEd Tanous         response.body().str() = std::move(bodyPart);
21827b0cf90SEd Tanous     }
21927b0cf90SEd Tanous 
2202d6cb56bSEd Tanous     void end()
2212d6cb56bSEd Tanous     {
2222d6cb56bSEd Tanous         std::string etag = computeEtag();
2232d6cb56bSEd Tanous         if (!etag.empty())
2242d6cb56bSEd Tanous         {
22527b0cf90SEd Tanous             addHeader(http::field::etag, etag);
22689f18008SEd Tanous         }
22704e438cbSEd Tanous         if (completed)
22804e438cbSEd Tanous         {
22962598e31SEd Tanous             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
23004e438cbSEd Tanous             return;
23104e438cbSEd Tanous         }
23204e438cbSEd Tanous         completed = true;
23362598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
23404e438cbSEd Tanous         if (completeRequestHandler)
23504e438cbSEd Tanous         {
23662598e31SEd Tanous             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
23772374eb7SNan Zhou             completeRequestHandler(*this);
23804e438cbSEd Tanous         }
23904e438cbSEd Tanous     }
24004e438cbSEd Tanous 
24172374eb7SNan Zhou     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
2424147b8acSJohn Edward Broadbent     {
24362598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
24472374eb7SNan Zhou         completeRequestHandler = std::move(handler);
24513548d85SEd Tanous 
24613548d85SEd Tanous         // Now that we have a new completion handler attached, we're no longer
24713548d85SEd Tanous         // complete
24813548d85SEd Tanous         completed = false;
24972374eb7SNan Zhou     }
25072374eb7SNan Zhou 
25172374eb7SNan Zhou     std::function<void(Response&)> releaseCompleteRequestHandler()
25272374eb7SNan Zhou     {
25362598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
25462598e31SEd Tanous                          static_cast<bool>(completeRequestHandler));
25572374eb7SNan Zhou         std::function<void(Response&)> ret = completeRequestHandler;
25672374eb7SNan Zhou         completeRequestHandler = nullptr;
25713548d85SEd Tanous         completed = true;
25872374eb7SNan Zhou         return ret;
25972374eb7SNan Zhou     }
26072374eb7SNan Zhou 
261291d709dSEd Tanous     void setHashAndHandleNotModified()
262291d709dSEd Tanous     {
263291d709dSEd Tanous         // Can only hash if we have content that's valid
26427b0cf90SEd Tanous         if (jsonValue.empty() || result() != http::status::ok)
265291d709dSEd Tanous         {
266291d709dSEd Tanous             return;
267291d709dSEd Tanous         }
268291d709dSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
269291d709dSEd Tanous         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
27027b0cf90SEd Tanous         addHeader(http::field::etag, hexVal);
271291d709dSEd Tanous         if (expectedHash && hexVal == *expectedHash)
272291d709dSEd Tanous         {
273a6695a84SEd Tanous             jsonValue = nullptr;
27427b0cf90SEd Tanous             result(http::status::not_modified);
275291d709dSEd Tanous         }
276291d709dSEd Tanous     }
277291d709dSEd Tanous 
278291d709dSEd Tanous     void setExpectedHash(std::string_view hash)
279291d709dSEd Tanous     {
280291d709dSEd Tanous         expectedHash = hash;
281291d709dSEd Tanous     }
282291d709dSEd Tanous 
283b5f288d2SAbhilash Raju     bool openFile(const std::filesystem::path& path,
284b5f288d2SAbhilash Raju                   bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
28527b0cf90SEd Tanous     {
28627b0cf90SEd Tanous         boost::beast::error_code ec;
287*52e31629SEd Tanous         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
288*52e31629SEd Tanous         response.body().encodingType = enc;
28927b0cf90SEd Tanous         if (ec)
29027b0cf90SEd Tanous         {
291*52e31629SEd Tanous             BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str());
29227b0cf90SEd Tanous             return false;
29327b0cf90SEd Tanous         }
294b5f288d2SAbhilash Raju         return true;
295b5f288d2SAbhilash Raju     }
296b5f288d2SAbhilash Raju 
297b5f288d2SAbhilash Raju     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
298b5f288d2SAbhilash Raju     {
299b5f288d2SAbhilash Raju         boost::beast::error_code ec;
300*52e31629SEd Tanous         response.body().encodingType = enc;
301*52e31629SEd Tanous         response.body().setFd(fd, ec);
302b5f288d2SAbhilash Raju         if (ec)
303b5f288d2SAbhilash Raju         {
304b5f288d2SAbhilash Raju             BMCWEB_LOG_ERROR("Failed to set fd");
305b5f288d2SAbhilash Raju             return false;
306b5f288d2SAbhilash Raju         }
307b5f288d2SAbhilash Raju         return true;
308b5f288d2SAbhilash Raju     }
309b5f288d2SAbhilash Raju 
310b5f288d2SAbhilash Raju   private:
311291d709dSEd Tanous     std::optional<std::string> expectedHash;
31272374eb7SNan Zhou     bool completed = false;
31372374eb7SNan Zhou     std::function<void(Response&)> completeRequestHandler;
31404e438cbSEd Tanous };
31504e438cbSEd Tanous } // namespace crow
316