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