1 #pragma once 2 #include "logging.hpp" 3 #include "utils/hex_utils.hpp" 4 5 #include <boost/beast/http/file_body.hpp> 6 #include <boost/beast/http/message.hpp> 7 #include <boost/beast/http/message_generator.hpp> 8 #include <boost/beast/http/string_body.hpp> 9 #include <boost/variant2/variant.hpp> 10 #include <nlohmann/json.hpp> 11 12 #include <optional> 13 #include <string> 14 #include <string_view> 15 16 namespace crow 17 { 18 19 template <typename Adaptor, typename Handler> 20 class Connection; 21 22 namespace http = boost::beast::http; 23 24 struct Response 25 { 26 template <typename Adaptor, typename Handler> 27 friend class crow::Connection; 28 29 using string_response = http::response<http::string_body>; 30 using file_response = http::response<http::file_body>; 31 32 // Use boost variant2 because it doesn't have valueless by exception 33 boost::variant2::variant<string_response, file_response> response; 34 35 nlohmann::json jsonValue; 36 using fields_type = http::header<false, http::fields>; 37 fields_type& fields() 38 { 39 return boost::variant2::visit( 40 [](auto&& r) -> fields_type& { return r.base(); }, response); 41 } 42 43 const fields_type& fields() const 44 { 45 return boost::variant2::visit( 46 [](auto&& r) -> const fields_type& { return r.base(); }, response); 47 } 48 49 void addHeader(std::string_view key, std::string_view value) 50 { 51 fields().insert(key, value); 52 } 53 54 void addHeader(http::field key, std::string_view value) 55 { 56 fields().insert(key, value); 57 } 58 59 void clearHeader(http::field key) 60 { 61 fields().erase(key); 62 } 63 64 Response() : response(string_response()) {} 65 Response(Response&& res) noexcept : 66 response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 67 completed(res.completed) 68 { 69 // See note in operator= move handler for why this is needed. 70 if (!res.completed) 71 { 72 completeRequestHandler = std::move(res.completeRequestHandler); 73 res.completeRequestHandler = nullptr; 74 } 75 isAliveHelper = res.isAliveHelper; 76 res.isAliveHelper = nullptr; 77 } 78 79 ~Response() = default; 80 81 Response(const Response&) = delete; 82 Response& operator=(const Response& r) = delete; 83 84 Response& operator=(Response&& r) noexcept 85 { 86 BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 87 logPtr(this), logPtr(&r)); 88 if (this == &r) 89 { 90 return *this; 91 } 92 response = std::move(r.response); 93 jsonValue = std::move(r.jsonValue); 94 95 // Only need to move completion handler if not already completed 96 // Note, there are cases where we might move out of a Response object 97 // while in a completion handler for that response object. This check 98 // is intended to prevent destructing the functor we are currently 99 // executing from in that case. 100 if (!r.completed) 101 { 102 completeRequestHandler = std::move(r.completeRequestHandler); 103 r.completeRequestHandler = nullptr; 104 } 105 else 106 { 107 completeRequestHandler = nullptr; 108 } 109 completed = r.completed; 110 isAliveHelper = std::move(r.isAliveHelper); 111 r.isAliveHelper = nullptr; 112 return *this; 113 } 114 115 void result(unsigned v) 116 { 117 fields().result(v); 118 } 119 120 void result(http::status v) 121 { 122 fields().result(v); 123 } 124 125 void copyBody(const Response& res) 126 { 127 const string_response* s = 128 boost::variant2::get_if<string_response>(&(res.response)); 129 if (s == nullptr) 130 { 131 BMCWEB_LOG_ERROR("Unable to copy a file"); 132 return; 133 } 134 string_response* myString = 135 boost::variant2::get_if<string_response>(&response); 136 if (myString == nullptr) 137 { 138 myString = &response.emplace<string_response>(); 139 } 140 myString->body() = s->body(); 141 } 142 143 http::status result() const 144 { 145 return fields().result(); 146 } 147 148 unsigned resultInt() const 149 { 150 return fields().result_int(); 151 } 152 153 std::string_view reason() const 154 { 155 return fields().reason(); 156 } 157 158 bool isCompleted() const noexcept 159 { 160 return completed; 161 } 162 163 const std::string* body() 164 { 165 string_response* body = 166 boost::variant2::get_if<string_response>(&response); 167 if (body == nullptr) 168 { 169 return nullptr; 170 } 171 return &body->body(); 172 } 173 174 std::string_view getHeaderValue(std::string_view key) const 175 { 176 return fields()[key]; 177 } 178 179 void keepAlive(bool k) 180 { 181 return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); }, 182 response); 183 } 184 185 bool keepAlive() const 186 { 187 return boost::variant2::visit([](auto&& r) { return r.keep_alive(); }, 188 response); 189 } 190 191 uint64_t getContentLength(boost::optional<uint64_t> pSize) 192 { 193 // This code is a throw-free equivalent to 194 // beast::http::message::prepare_payload 195 using http::status; 196 using http::status_class; 197 using http::to_status_class; 198 if (!pSize) 199 { 200 return 0; 201 } 202 bool is1XXReturn = to_status_class(result()) == 203 status_class::informational; 204 if (*pSize > 0 && (is1XXReturn || result() == status::no_content || 205 result() == status::not_modified)) 206 { 207 BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 208 "no-content or not_modified, which aren't " 209 "allowed to have a body", 210 logPtr(this)); 211 return 0; 212 } 213 return *pSize; 214 } 215 216 uint64_t size() 217 { 218 return boost::variant2::visit( 219 [](auto&& res) -> uint64_t { return res.body().size(); }, response); 220 } 221 222 void preparePayload() 223 { 224 boost::variant2::visit( 225 [this](auto&& r) { 226 r.content_length(getContentLength(r.payload_size())); 227 }, 228 response); 229 } 230 231 void clear() 232 { 233 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 234 response.emplace<string_response>(); 235 jsonValue = nullptr; 236 completed = false; 237 expectedHash = std::nullopt; 238 } 239 240 std::string computeEtag() const 241 { 242 // Only set etag if this request succeeded 243 if (result() != http::status::ok) 244 { 245 return ""; 246 } 247 // and the json response isn't empty 248 if (jsonValue.empty()) 249 { 250 return ""; 251 } 252 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 253 return "\"" + intToHexString(hashval, 8) + "\""; 254 } 255 256 void write(std::string&& bodyPart) 257 { 258 string_response* str = 259 boost::variant2::get_if<string_response>(&response); 260 if (str != nullptr) 261 { 262 str->body() += bodyPart; 263 return; 264 } 265 response.emplace<string_response>(result(), 11, std::move(bodyPart)); 266 } 267 268 void end() 269 { 270 std::string etag = computeEtag(); 271 if (!etag.empty()) 272 { 273 addHeader(http::field::etag, etag); 274 } 275 if (completed) 276 { 277 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 278 return; 279 } 280 completed = true; 281 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 282 if (completeRequestHandler) 283 { 284 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 285 completeRequestHandler(*this); 286 } 287 } 288 289 bool isAlive() const 290 { 291 return isAliveHelper && isAliveHelper(); 292 } 293 294 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 295 { 296 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 297 completeRequestHandler = std::move(handler); 298 299 // Now that we have a new completion handler attached, we're no longer 300 // complete 301 completed = false; 302 } 303 304 std::function<void(Response&)> releaseCompleteRequestHandler() 305 { 306 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 307 static_cast<bool>(completeRequestHandler)); 308 std::function<void(Response&)> ret = completeRequestHandler; 309 completeRequestHandler = nullptr; 310 completed = true; 311 return ret; 312 } 313 314 void setIsAliveHelper(std::function<bool()>&& handler) 315 { 316 isAliveHelper = std::move(handler); 317 } 318 319 std::function<bool()> releaseIsAliveHelper() 320 { 321 std::function<bool()> ret = std::move(isAliveHelper); 322 isAliveHelper = nullptr; 323 return ret; 324 } 325 326 void setHashAndHandleNotModified() 327 { 328 // Can only hash if we have content that's valid 329 if (jsonValue.empty() || result() != http::status::ok) 330 { 331 return; 332 } 333 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 334 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 335 addHeader(http::field::etag, hexVal); 336 if (expectedHash && hexVal == *expectedHash) 337 { 338 jsonValue = nullptr; 339 result(http::status::not_modified); 340 } 341 } 342 343 void setExpectedHash(std::string_view hash) 344 { 345 expectedHash = hash; 346 } 347 348 using message_generator = http::message_generator; 349 message_generator generator() 350 { 351 return boost::variant2::visit( 352 [](auto& r) -> message_generator { return std::move(r); }, 353 response); 354 } 355 356 bool openFile(const std::filesystem::path& path) 357 { 358 http::file_body::value_type file; 359 boost::beast::error_code ec; 360 file.open(path.c_str(), boost::beast::file_mode::read, ec); 361 if (ec) 362 { 363 return false; 364 } 365 // store the headers on stack temporarily so we can reconstruct the new 366 // base with the old headers copied in. 367 http::header<false> headTemp = std::move(fields()); 368 file_response& fileResponse = 369 response.emplace<file_response>(std::move(headTemp)); 370 fileResponse.body() = std::move(file); 371 return true; 372 } 373 374 private: 375 std::optional<std::string> expectedHash; 376 bool completed = false; 377 std::function<void(Response&)> completeRequestHandler; 378 std::function<bool()> isAliveHelper; 379 }; 380 } // namespace crow 381