1 #pragma once 2 #include "http_file_body.hpp" 3 #include "logging.hpp" 4 #include "utils/hex_utils.hpp" 5 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 #include <utility> 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<bmcweb::FileBody>; 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 http::header<false> headTemp = std::move(fields()); 266 string_response& stringResponse = 267 response.emplace<string_response>(std::move(headTemp)); 268 stringResponse.body() = std::move(bodyPart); 269 } 270 271 void end() 272 { 273 std::string etag = computeEtag(); 274 if (!etag.empty()) 275 { 276 addHeader(http::field::etag, etag); 277 } 278 if (completed) 279 { 280 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 281 return; 282 } 283 completed = true; 284 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 285 if (completeRequestHandler) 286 { 287 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 288 completeRequestHandler(*this); 289 } 290 } 291 292 bool isAlive() const 293 { 294 return isAliveHelper && isAliveHelper(); 295 } 296 297 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 298 { 299 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 300 completeRequestHandler = std::move(handler); 301 302 // Now that we have a new completion handler attached, we're no longer 303 // complete 304 completed = false; 305 } 306 307 std::function<void(Response&)> releaseCompleteRequestHandler() 308 { 309 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 310 static_cast<bool>(completeRequestHandler)); 311 std::function<void(Response&)> ret = completeRequestHandler; 312 completeRequestHandler = nullptr; 313 completed = true; 314 return ret; 315 } 316 317 void setIsAliveHelper(std::function<bool()>&& handler) 318 { 319 isAliveHelper = std::move(handler); 320 } 321 322 std::function<bool()> releaseIsAliveHelper() 323 { 324 std::function<bool()> ret = std::move(isAliveHelper); 325 isAliveHelper = nullptr; 326 return ret; 327 } 328 329 void setHashAndHandleNotModified() 330 { 331 // Can only hash if we have content that's valid 332 if (jsonValue.empty() || result() != http::status::ok) 333 { 334 return; 335 } 336 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 337 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 338 addHeader(http::field::etag, hexVal); 339 if (expectedHash && hexVal == *expectedHash) 340 { 341 jsonValue = nullptr; 342 result(http::status::not_modified); 343 } 344 } 345 346 void setExpectedHash(std::string_view hash) 347 { 348 expectedHash = hash; 349 } 350 351 using message_generator = http::message_generator; 352 message_generator generator() 353 { 354 return boost::variant2::visit( 355 [](auto& r) -> message_generator { return std::move(r); }, 356 response); 357 } 358 359 bool openFile(const std::filesystem::path& path, 360 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 361 { 362 file_response::body_type::value_type body(enc); 363 boost::beast::error_code ec; 364 body.open(path.c_str(), boost::beast::file_mode::read, ec); 365 if (ec) 366 { 367 return false; 368 } 369 updateFileBody(std::move(body)); 370 return true; 371 } 372 373 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 374 { 375 file_response::body_type::value_type body(enc); 376 boost::beast::error_code ec; 377 body.setFd(fd, ec); 378 if (ec) 379 { 380 BMCWEB_LOG_ERROR("Failed to set fd"); 381 return false; 382 } 383 updateFileBody(std::move(body)); 384 return true; 385 } 386 387 private: 388 void updateFileBody(file_response::body_type::value_type file) 389 { 390 // store the headers on stack temporarily so we can reconstruct the new 391 // base with the old headers copied in. 392 http::header<false> headTemp = std::move(fields()); 393 file_response& fileResponse = 394 response.emplace<file_response>(std::move(headTemp)); 395 fileResponse.body() = std::move(file); 396 } 397 398 std::optional<std::string> expectedHash; 399 bool completed = false; 400 std::function<void(Response&)> completeRequestHandler; 401 std::function<bool()> isAliveHelper; 402 }; 403 } // namespace crow 404