| #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 |