#pragma once #include "logging.hpp" #include "nlohmann/json.hpp" #include "utils/hex_utils.hpp" #include #include #include #include #include namespace crow { template class Connection; struct Response { template friend class crow::Connection; using response_type = boost::beast::http::response; std::optional stringResponse; nlohmann::json jsonValue; void addHeader(std::string_view key, std::string_view value) { stringResponse->set(key, value); } void addHeader(boost::beast::http::field key, std::string_view value) { stringResponse->set(key, value); } Response() : stringResponse(response_type{}) {} Response(Response&& res) noexcept : stringResponse(std::move(res.stringResponse)), 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: " << this << "; other: " << &r; if (this == &r) { return *this; } stringResponse = std::move(r.stringResponse); r.stringResponse.emplace(response_type{}); 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) { stringResponse->result(v); } void result(boost::beast::http::status v) { stringResponse->result(v); } boost::beast::http::status result() const { return stringResponse->result(); } unsigned resultInt() const { return stringResponse->result_int(); } std::string_view reason() const { return stringResponse->reason(); } bool isCompleted() const noexcept { return completed; } std::string& body() { return stringResponse->body(); } std::string_view getHeaderValue(std::string_view key) const { return stringResponse->base()[key]; } void keepAlive(bool k) { stringResponse->keep_alive(k); } bool keepAlive() const { return stringResponse->keep_alive(); } void preparePayload() { stringResponse->prepare_payload(); } void clear() { BMCWEB_LOG_DEBUG << this << " Clearing response containers"; stringResponse.emplace(response_type{}); jsonValue.clear(); completed = false; expectedHash = std::nullopt; } void write(std::string_view bodyPart) { stringResponse->body() += std::string(bodyPart); } std::string 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{}(jsonValue); return "\"" + intToHexString(hashval, 8) + "\""; } void 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); } } bool isAlive() const { return isAliveHelper && isAliveHelper(); } void setCompleteRequestHandler(std::function&& handler) { BMCWEB_LOG_DEBUG << this << " setting completion handler"; completeRequestHandler = std::move(handler); // Now that we have a new completion handler attached, we're no longer // complete completed = false; } std::function releaseCompleteRequestHandler() { BMCWEB_LOG_DEBUG << this << " releasing completion handler" << static_cast(completeRequestHandler); std::function ret = completeRequestHandler; completeRequestHandler = nullptr; completed = true; return ret; } void setIsAliveHelper(std::function&& handler) { isAliveHelper = std::move(handler); } std::function releaseIsAliveHelper() { std::function ret = std::move(isAliveHelper); isAliveHelper = nullptr; return ret; } void 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{}(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 setExpectedHash(std::string_view hash) { expectedHash = hash; } private: std::optional expectedHash; bool completed = false; std::function completeRequestHandler; std::function isAliveHelper; }; } // namespace crow