1 #pragma once 2 #include "logging.hpp" 3 #include "nlohmann/json.hpp" 4 #include "utils/hex_utils.hpp" 5 6 #include <boost/beast/http/message.hpp> 7 #include <boost/beast/http/string_body.hpp> 8 9 #include <optional> 10 #include <string> 11 #include <string_view> 12 13 namespace crow 14 { 15 16 template <typename Adaptor, typename Handler> 17 class Connection; 18 19 struct Response 20 { 21 template <typename Adaptor, typename Handler> 22 friend class crow::Connection; 23 using response_type = 24 boost::beast::http::response<boost::beast::http::string_body>; 25 26 std::optional<response_type> stringResponse; 27 28 nlohmann::json jsonValue; 29 30 void addHeader(std::string_view key, std::string_view value) 31 { 32 stringResponse->set(key, value); 33 } 34 35 void addHeader(boost::beast::http::field key, std::string_view value) 36 { 37 stringResponse->set(key, value); 38 } 39 40 Response() : stringResponse(response_type{}) {} 41 42 Response(Response&& res) noexcept : 43 stringResponse(std::move(res.stringResponse)), 44 jsonValue(std::move(res.jsonValue)), completed(res.completed) 45 { 46 // See note in operator= move handler for why this is needed. 47 if (!res.completed) 48 { 49 completeRequestHandler = std::move(res.completeRequestHandler); 50 res.completeRequestHandler = nullptr; 51 } 52 isAliveHelper = res.isAliveHelper; 53 res.isAliveHelper = nullptr; 54 } 55 56 ~Response() = default; 57 58 Response(const Response&) = delete; 59 60 Response& operator=(const Response& r) = delete; 61 62 Response& operator=(Response&& r) noexcept 63 { 64 BMCWEB_LOG_DEBUG << "Moving response containers; this: " << this 65 << "; other: " << &r; 66 if (this == &r) 67 { 68 return *this; 69 } 70 stringResponse = std::move(r.stringResponse); 71 r.stringResponse.emplace(response_type{}); 72 jsonValue = std::move(r.jsonValue); 73 74 // Only need to move completion handler if not already completed 75 // Note, there are cases where we might move out of a Response object 76 // while in a completion handler for that response object. This check 77 // is intended to prevent destructing the functor we are currently 78 // executing from in that case. 79 if (!r.completed) 80 { 81 completeRequestHandler = std::move(r.completeRequestHandler); 82 r.completeRequestHandler = nullptr; 83 } 84 else 85 { 86 completeRequestHandler = nullptr; 87 } 88 completed = r.completed; 89 isAliveHelper = std::move(r.isAliveHelper); 90 r.isAliveHelper = nullptr; 91 return *this; 92 } 93 94 void result(unsigned v) 95 { 96 stringResponse->result(v); 97 } 98 99 void result(boost::beast::http::status v) 100 { 101 stringResponse->result(v); 102 } 103 104 boost::beast::http::status result() const 105 { 106 return stringResponse->result(); 107 } 108 109 unsigned resultInt() const 110 { 111 return stringResponse->result_int(); 112 } 113 114 std::string_view reason() const 115 { 116 return stringResponse->reason(); 117 } 118 119 bool isCompleted() const noexcept 120 { 121 return completed; 122 } 123 124 std::string& body() 125 { 126 return stringResponse->body(); 127 } 128 129 std::string_view getHeaderValue(std::string_view key) const 130 { 131 return stringResponse->base()[key]; 132 } 133 134 void keepAlive(bool k) 135 { 136 stringResponse->keep_alive(k); 137 } 138 139 bool keepAlive() const 140 { 141 return stringResponse->keep_alive(); 142 } 143 144 void preparePayload() 145 { 146 stringResponse->prepare_payload(); 147 } 148 149 void clear() 150 { 151 BMCWEB_LOG_DEBUG << this << " Clearing response containers"; 152 stringResponse.emplace(response_type{}); 153 jsonValue.clear(); 154 completed = false; 155 expectedHash = std::nullopt; 156 } 157 158 void write(std::string_view bodyPart) 159 { 160 stringResponse->body() += std::string(bodyPart); 161 } 162 163 std::string computeEtag() const 164 { 165 // Only set etag if this request succeeded 166 if (result() != boost::beast::http::status::ok) 167 { 168 return ""; 169 } 170 // and the json response isn't empty 171 if (jsonValue.empty()) 172 { 173 return ""; 174 } 175 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 176 return "\"" + intToHexString(hashval, 8) + "\""; 177 } 178 179 void end() 180 { 181 std::string etag = computeEtag(); 182 if (!etag.empty()) 183 { 184 addHeader(boost::beast::http::field::etag, etag); 185 } 186 if (completed) 187 { 188 BMCWEB_LOG_ERROR << this << " Response was ended twice"; 189 return; 190 } 191 completed = true; 192 BMCWEB_LOG_DEBUG << this << " calling completion handler"; 193 if (completeRequestHandler) 194 { 195 BMCWEB_LOG_DEBUG << this << " completion handler was valid"; 196 completeRequestHandler(*this); 197 } 198 } 199 200 bool isAlive() const 201 { 202 return isAliveHelper && isAliveHelper(); 203 } 204 205 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 206 { 207 BMCWEB_LOG_DEBUG << this << " setting completion handler"; 208 completeRequestHandler = std::move(handler); 209 210 // Now that we have a new completion handler attached, we're no longer 211 // complete 212 completed = false; 213 } 214 215 std::function<void(Response&)> releaseCompleteRequestHandler() 216 { 217 BMCWEB_LOG_DEBUG << this << " releasing completion handler" 218 << static_cast<bool>(completeRequestHandler); 219 std::function<void(Response&)> ret = completeRequestHandler; 220 completeRequestHandler = nullptr; 221 completed = true; 222 return ret; 223 } 224 225 void setIsAliveHelper(std::function<bool()>&& handler) 226 { 227 isAliveHelper = std::move(handler); 228 } 229 230 std::function<bool()> releaseIsAliveHelper() 231 { 232 std::function<bool()> ret = std::move(isAliveHelper); 233 isAliveHelper = nullptr; 234 return ret; 235 } 236 237 void setHashAndHandleNotModified() 238 { 239 // Can only hash if we have content that's valid 240 if (jsonValue.empty() || result() != boost::beast::http::status::ok) 241 { 242 return; 243 } 244 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 245 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 246 addHeader(boost::beast::http::field::etag, hexVal); 247 if (expectedHash && hexVal == *expectedHash) 248 { 249 jsonValue.clear(); 250 result(boost::beast::http::status::not_modified); 251 } 252 } 253 254 void setExpectedHash(std::string_view hash) 255 { 256 expectedHash = hash; 257 } 258 259 private: 260 std::optional<std::string> expectedHash; 261 bool completed = false; 262 std::function<void(Response&)> completeRequestHandler; 263 std::function<bool()> isAliveHelper; 264 }; 265 } // namespace crow 266