diff options
Diffstat (limited to 'http/http_response.hpp')
-rw-r--r-- | http/http_response.hpp | 202 |
1 files changed, 143 insertions, 59 deletions
diff --git a/http/http_response.hpp b/http/http_response.hpp index cb07a83635..a5f95a90b2 100644 --- a/http/http_response.hpp +++ b/http/http_response.hpp @@ -2,8 +2,11 @@ #include "logging.hpp" #include "utils/hex_utils.hpp" +#include <boost/beast/http/file_body.hpp> #include <boost/beast/http/message.hpp> +#include <boost/beast/http/message_generator.hpp> #include <boost/beast/http/string_body.hpp> +#include <boost/variant2/variant.hpp> #include <nlohmann/json.hpp> #include <optional> @@ -16,37 +19,52 @@ namespace crow template <typename Adaptor, typename Handler> class Connection; +namespace http = boost::beast::http; + struct Response { template <typename Adaptor, typename Handler> friend class crow::Connection; - using response_type = - boost::beast::http::response<boost::beast::http::string_body>; - response_type stringResponse; + using string_response = http::response<http::string_body>; + using file_response = http::response<http::file_body>; + + // Use boost variant2 because it doesn't have valueless by exception + boost::variant2::variant<string_response, file_response> response; nlohmann::json jsonValue; + using fields_type = http::header<false, http::fields>; + fields_type& fields() + { + return boost::variant2::visit( + [](auto&& r) -> fields_type& { return r.base(); }, response); + } - void addHeader(std::string_view key, std::string_view value) + const fields_type& fields() const { - stringResponse.insert(key, value); + return boost::variant2::visit( + [](auto&& r) -> const fields_type& { return r.base(); }, response); } - void addHeader(boost::beast::http::field key, std::string_view value) + void addHeader(std::string_view key, std::string_view value) { - stringResponse.insert(key, value); + fields().insert(key, value); } - void clearHeader(boost::beast::http::field key) + void addHeader(http::field key, std::string_view value) { - stringResponse.erase(key); + fields().insert(key, value); } - Response() = default; + void clearHeader(http::field key) + { + fields().erase(key); + } + Response() : response(string_response()) {} Response(Response&& res) noexcept : - stringResponse(std::move(res.stringResponse)), - jsonValue(std::move(res.jsonValue)), completed(res.completed) + response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), + completed(res.completed) { // See note in operator= move handler for why this is needed. if (!res.completed) @@ -61,7 +79,6 @@ struct Response ~Response() = default; Response(const Response&) = delete; - Response& operator=(const Response& r) = delete; Response& operator=(Response&& r) noexcept @@ -72,8 +89,7 @@ struct Response { return *this; } - stringResponse = std::move(r.stringResponse); - r.stringResponse.clear(); + response = std::move(r.response); jsonValue = std::move(r.jsonValue); // Only need to move completion handler if not already completed @@ -98,27 +114,45 @@ struct Response void result(unsigned v) { - stringResponse.result(v); + fields().result(v); } - void result(boost::beast::http::status v) + void result(http::status v) { - stringResponse.result(v); + fields().result(v); } - boost::beast::http::status result() const + void copyBody(const Response& res) { - return stringResponse.result(); + const string_response* s = + boost::variant2::get_if<string_response>(&(res.response)); + if (s == nullptr) + { + BMCWEB_LOG_ERROR("Unable to copy a file"); + return; + } + string_response* myString = + boost::variant2::get_if<string_response>(&response); + if (myString == nullptr) + { + myString = &response.emplace<string_response>(); + } + myString->body() = s->body(); + } + + http::status result() const + { + return fields().result(); } unsigned resultInt() const { - return stringResponse.result_int(); + return fields().result_int(); } std::string_view reason() const { - return stringResponse.reason(); + return fields().reason(); } bool isCompleted() const noexcept @@ -126,75 +160,87 @@ struct Response return completed; } - std::string& body() + const std::string* body() { - return stringResponse.body(); + string_response* body = + boost::variant2::get_if<string_response>(&response); + if (body == nullptr) + { + return nullptr; + } + return &body->body(); } std::string_view getHeaderValue(std::string_view key) const { - return stringResponse.base()[key]; + return fields()[key]; } void keepAlive(bool k) { - stringResponse.keep_alive(k); + return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); }, + response); } bool keepAlive() const { - return stringResponse.keep_alive(); + return boost::variant2::visit([](auto&& r) { return r.keep_alive(); }, + response); } - void preparePayload() + uint64_t getContentLength(boost::optional<uint64_t> pSize) { // This code is a throw-free equivalent to // beast::http::message::prepare_payload - boost::optional<uint64_t> pSize = stringResponse.payload_size(); - using boost::beast::http::status; - using boost::beast::http::status_class; - using boost::beast::http::to_status_class; + using http::status; + using http::status_class; + using http::to_status_class; if (!pSize) { - pSize = 0; + return 0; } - else + bool is1XXReturn = to_status_class(result()) == + status_class::informational; + if (*pSize > 0 && (is1XXReturn || result() == status::no_content || + result() == status::not_modified)) { - bool is1XXReturn = to_status_class(stringResponse.result()) == - status_class::informational; - if (*pSize > 0 && - (is1XXReturn || stringResponse.result() == status::no_content || - stringResponse.result() == status::not_modified)) - { - BMCWEB_LOG_CRITICAL( - "{} Response content provided but code was no-content or not_modified, which aren't allowed to have a body", - logPtr(this)); - pSize = 0; - body().clear(); - } + BMCWEB_LOG_CRITICAL("{} Response content provided but code was " + "no-content or not_modified, which aren't " + "allowed to have a body", + logPtr(this)); + return 0; } - stringResponse.content_length(*pSize); + return *pSize; + } + + uint64_t size() + { + return boost::variant2::visit( + [](auto&& res) -> uint64_t { return res.body().size(); }, response); + } + + void preparePayload() + { + boost::variant2::visit( + [this](auto&& r) { + r.content_length(getContentLength(r.payload_size())); + }, + response); } void clear() { BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); - stringResponse.clear(); - stringResponse.body().shrink_to_fit(); + response.emplace<string_response>(); jsonValue = nullptr; completed = false; expectedHash = std::nullopt; } - void write(std::string_view bodyPart) - { - stringResponse.body() += std::string(bodyPart); - } - std::string computeEtag() const { // Only set etag if this request succeeded - if (result() != boost::beast::http::status::ok) + if (result() != http::status::ok) { return ""; } @@ -207,12 +253,24 @@ struct Response return "\"" + intToHexString(hashval, 8) + "\""; } + void write(std::string&& bodyPart) + { + string_response* str = + boost::variant2::get_if<string_response>(&response); + if (str != nullptr) + { + str->body() += bodyPart; + return; + } + response.emplace<string_response>(result(), 11, std::move(bodyPart)); + } + void end() { std::string etag = computeEtag(); if (!etag.empty()) { - addHeader(boost::beast::http::field::etag, etag); + addHeader(http::field::etag, etag); } if (completed) { @@ -268,17 +326,17 @@ struct Response void setHashAndHandleNotModified() { // Can only hash if we have content that's valid - if (jsonValue.empty() || result() != boost::beast::http::status::ok) + if (jsonValue.empty() || result() != http::status::ok) { return; } size_t hashval = std::hash<nlohmann::json>{}(jsonValue); std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; - addHeader(boost::beast::http::field::etag, hexVal); + addHeader(http::field::etag, hexVal); if (expectedHash && hexVal == *expectedHash) { jsonValue = nullptr; - result(boost::beast::http::status::not_modified); + result(http::status::not_modified); } } @@ -287,6 +345,32 @@ struct Response expectedHash = hash; } + using message_generator = http::message_generator; + message_generator generator() + { + return boost::variant2::visit( + [](auto& r) -> message_generator { return std::move(r); }, + response); + } + + bool openFile(const std::filesystem::path& path) + { + http::file_body::value_type file; + boost::beast::error_code ec; + file.open(path.c_str(), boost::beast::file_mode::read, ec); + if (ec) + { + return false; + } + // store the headers on stack temporarily so we can reconstruct the new + // base with the old headers copied in. + http::header<false> headTemp = std::move(fields()); + file_response& fileResponse = + response.emplace<file_response>(std::move(headTemp)); + fileResponse.body() = std::move(file); + return true; + } + private: std::optional<std::string> expectedHash; bool completed = false; |