blob: ec54a90c4b02923f3f7dc28cc874b1290173c6f8 [file] [log] [blame]
#pragma once
#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>
#include <string>
#include <string_view>
#include <utility>
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 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);
}
const fields_type& fields() const
{
return boost::variant2::visit(
[](auto&& r) -> const fields_type& { return r.base(); }, response);
}
void addHeader(std::string_view key, std::string_view value)
{
fields().insert(key, value);
}
void addHeader(http::field key, std::string_view value)
{
fields().insert(key, value);
}
void clearHeader(http::field key)
{
fields().erase(key);
}
Response() : response(string_response()) {}
Response(Response&& res) noexcept :
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)
{
completeRequestHandler = std::move(res.completeRequestHandler);
res.completeRequestHandler = nullptr;
}
isAliveHelper = res.isAliveHelper;
res.isAliveHelper = nullptr;
}
~Response() = default;
Response(const Response&) = delete;
Response& operator=(const Response& r) = delete;
Response& operator=(Response&& r) noexcept
{
BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
logPtr(this), logPtr(&r));
if (this == &r)
{
return *this;
}
response = std::move(r.response);
jsonValue = std::move(r.jsonValue);
// Only need to move completion handler if not already completed
// Note, there are cases where we might move out of a Response object
// while in a completion handler for that response object. This check
// is intended to prevent destructing the functor we are currently
// executing from in that case.
if (!r.completed)
{
completeRequestHandler = std::move(r.completeRequestHandler);
r.completeRequestHandler = nullptr;
}
else
{
completeRequestHandler = nullptr;
}
completed = r.completed;
isAliveHelper = std::move(r.isAliveHelper);
r.isAliveHelper = nullptr;
return *this;
}
void result(unsigned v)
{
fields().result(v);
}
void result(http::status v)
{
fields().result(v);
}
void copyBody(const Response& res)
{
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 fields().result_int();
}
std::string_view reason() const
{
return fields().reason();
}
bool isCompleted() const noexcept
{
return completed;
}
const std::string* 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 fields()[key];
}
void keepAlive(bool k)
{
return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); },
response);
}
bool keepAlive() const
{
return boost::variant2::visit([](auto&& r) { return r.keep_alive(); },
response);
}
uint64_t getContentLength(boost::optional<uint64_t> pSize)
{
// This code is a throw-free equivalent to
// beast::http::message::prepare_payload
using http::status;
using http::status_class;
using http::to_status_class;
if (!pSize)
{
return 0;
}
bool is1XXReturn = to_status_class(result()) ==
status_class::informational;
if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
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));
return 0;
}
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));
response.emplace<string_response>();
jsonValue = nullptr;
completed = false;
expectedHash = std::nullopt;
}
std::string computeEtag() const
{
// Only set etag if this request succeeded
if (result() != http::status::ok)
{
return "";
}
// and the json response isn't empty
if (jsonValue.empty())
{
return "";
}
size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
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;
}
http::header<false> headTemp = std::move(fields());
string_response& stringResponse =
response.emplace<string_response>(std::move(headTemp));
stringResponse.body() = std::move(bodyPart);
}
void end()
{
std::string etag = computeEtag();
if (!etag.empty())
{
addHeader(http::field::etag, etag);
}
if (completed)
{
BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
return;
}
completed = true;
BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
if (completeRequestHandler)
{
BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
completeRequestHandler(*this);
}
}
bool isAlive() const
{
return isAliveHelper && isAliveHelper();
}
void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
{
BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
completeRequestHandler = std::move(handler);
// Now that we have a new completion handler attached, we're no longer
// complete
completed = false;
}
std::function<void(Response&)> releaseCompleteRequestHandler()
{
BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
static_cast<bool>(completeRequestHandler));
std::function<void(Response&)> ret = completeRequestHandler;
completeRequestHandler = nullptr;
completed = true;
return ret;
}
void setIsAliveHelper(std::function<bool()>&& handler)
{
isAliveHelper = std::move(handler);
}
std::function<bool()> releaseIsAliveHelper()
{
std::function<bool()> ret = std::move(isAliveHelper);
isAliveHelper = nullptr;
return ret;
}
void setHashAndHandleNotModified()
{
// Can only hash if we have content that's valid
if (jsonValue.empty() || result() != http::status::ok)
{
return;
}
size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
addHeader(http::field::etag, hexVal);
if (expectedHash && hexVal == *expectedHash)
{
jsonValue = nullptr;
result(http::status::not_modified);
}
}
void setExpectedHash(std::string_view hash)
{
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;
std::function<void(Response&)> completeRequestHandler;
std::function<bool()> isAliveHelper;
};
} // namespace crow