blob: cabc0b15a2be3fb497d76fcad2aedc2d75a55c3f [file] [log] [blame]
#include "absl/strings/match.h"
#include "http_response.hpp"
#include "logging.hpp"
#include "managed_store.hpp"
#include "utils/hex_utils.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <nlohmann/json.hpp>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace crow
{
Response& Response::operator=(Response&& r) noexcept
{
BMCWEB_LOG_DEBUG << "Moving response containers; this: " << this
<< "; other: " << &r;
if (this == &r)
{
return *this;
}
stringResponse = std::move(r.stringResponse);
r.stringResponse.emplace(response_type{});
jsonValue = std::move(r.jsonValue);
for (const auto& [marker, timestamp] : r.markers)
{
markers.emplace(marker, timestamp);
}
// 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 Response::clear()
{
BMCWEB_LOG_DEBUG << this << " Clearing response containers";
stringResponse.emplace(response_type{});
jsonValue.clear();
completed = false;
expectedHash = std::nullopt;
}
std::string Response::computeEtag() const
{
// Only set etag if this request succeeded
if (result() != boost::beast::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 Response::setHashAndHandleNotModified()
{
// Can only hash if we have content that's valid
if (jsonValue.empty() || result() != boost::beast::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);
if (expectedHash && hexVal == *expectedHash)
{
jsonValue.clear();
result(boost::beast::http::status::not_modified);
}
}
void Response::end()
{
std::string etag = computeEtag();
if (!etag.empty())
{
addHeader(boost::beast::http::field::etag, etag);
}
if (completed)
{
BMCWEB_LOG_ERROR << this << " Response was ended twice";
return;
}
completed = true;
BMCWEB_LOG_DEBUG << this << " calling completion handler";
if (completeRequestHandler)
{
BMCWEB_LOG_DEBUG << this << " completion handler was valid";
completeRequestHandler(*this);
}
}
void Response::setCompleteRequestHandler(
std::function<void(Response&)>&& handler)
{
completeRequestHandler = std::move(handler);
BMCWEB_LOG_DEBUG << this << " setting completion handler"
<< &completeRequestHandler;
// Now that we have a new completion handler attached, we're no longer
// complete
completed = false;
}
std::function<void(Response&)> Response::releaseCompleteRequestHandler()
{
BMCWEB_LOG_DEBUG << this << " releasing completion handler"
<< &completeRequestHandler;
std::function<void(Response&)> ret = completeRequestHandler;
completeRequestHandler = nullptr;
completed = true;
return ret;
}
void Response::addTimetrace(const std::string& marker)
{
if (managedStore::GetManagedObjectStore()->getConfig().timetrace)
{
std::chrono::high_resolution_clock::time_point now =
std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count();
markers[marker] = ms;
}
}
void Response::startTimetrace(const std::string& block)
{
addTimetrace("START_" + block);
}
void Response::endTimetrace(const std::string& block)
{
addTimetrace("END_" + block);
}
void Response::requestStartTime(const std::string& uri)
{
addTimetrace("REQUEST_" + uri);
}
void Response::requestEndTime()
{
addTimetrace("REQUESTEND");
}
nlohmann::json Response::timetraceToJson()
{
nlohmann::json jsonObj;
if (managedStore::GetManagedObjectStore()->getConfig().timetrace)
{
nlohmann::json& hops = jsonObj["hops"];
hops = nlohmann::json::array();
for (const auto& [marker, timestamp] : markers)
{
if (absl::StrContains(marker, "START_"))
{
continue;
}
// process END_
size_t pos = marker.find("END_");
if (pos != std::string::npos)
{
// find the start and calculate the diff
std::string block = marker.substr(pos + 4);
auto startTime = markers["START_" + block];
auto duration = timestamp - startTime;
nlohmann::json::object_t hop;
hop["id"] = boost::algorithm::to_lower_copy(block);
hop["time_ms"] = duration;
hops.emplace_back(std::move(hop));
}
else if (absl::StrContains(marker, "REQUEST_"))
{
size_t uriPos = marker.find("REQUEST_");
std::string uri = marker.substr(uriPos + 8);
jsonObj["uri"] = uri;
jsonObj["starttime"] = timestamp;
}
else if (absl::StrContains(marker, "REQUESTEND"))
{
jsonObj["endtime"] = timestamp;
}
}
}
return jsonObj;
}
void Response::updateTraceInManagedStore()
{
if (managedStore::GetManagedObjectStore()->getConfig().timetrace)
{
nlohmann::json jsonObj = timetraceToJson();
managedStore::GetManagedObjectStore()->storeTimeTrace(jsonObj);
}
}
} // namespace crow