blob: daac0205a8fb051123df279c1afc6fe0636b4b9c [file] [log] [blame]
#include "http_response.hpp"
#include <chrono> // NOLINT
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include "absl/strings/match.h"
#include "boost/algorithm/string/case_conv.hpp" // NOLINT
#include "logging.hpp"
#include "hex_utils.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.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 {
// return empty string if we don't have a string response
if (!stringResponse.has_value()) {
return "";
}
// 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 hex_val = "\"" + intToHexString(hashval, 8) + "\"";
addHeader(boost::beast::http::field::etag, hex_val);
if (expectedHash && hex_val == *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 json_obj;
if (managedStore::GetManagedObjectStore()->getConfig().timetrace) {
nlohmann::json& hops = json_obj["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 start_time = markers["START_" + block];
auto duration = timestamp - start_time;
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 uri_pos = marker.find("REQUEST_");
std::string uri = marker.substr(uri_pos + 8);
json_obj["uri"] = uri;
json_obj["starttime"] = timestamp;
} else if (absl::StrContains(marker, "REQUESTEND")) {
json_obj["endtime"] = timestamp;
}
}
}
return json_obj;
}
void Response::updateTraceInManagedStore() {
if (managedStore::GetManagedObjectStore()->getConfig().timetrace) {
nlohmann::json json_obj = timetraceToJson();
managedStore::GetManagedObjectStore()->storeTimeTrace(json_obj);
}
}
} // namespace crow