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