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 84 // Only need to move completion handler if not already completed 85 // Note, there are cases where we might move out of a Response object 86 // while in a completion handler for that response object. This check 87 // is intended to prevent destructing the functor we are currently 88 // executing from in that case. 89 if (!r.completed) 90 { 91 completeRequestHandler = std::move(r.completeRequestHandler); 92 r.completeRequestHandler = nullptr; 93 } 94 else 95 { 96 completeRequestHandler = nullptr; 97 } 98 completed = r.completed; 99 return *this; 100 } 101 102 void result(unsigned v) 103 { 104 fields().result(v); 105 } 106 107 void result(http::status v) 108 { 109 fields().result(v); 110 } 111 112 void copyBody(const Response& res) 113 { 114 response.body() = res.response.body(); 115 } 116 117 http::status result() const 118 { 119 return fields().result(); 120 } 121 122 unsigned resultInt() const 123 { 124 return fields().result_int(); 125 } 126 127 std::string_view reason() const 128 { 129 return fields().reason(); 130 } 131 132 bool isCompleted() const noexcept 133 { 134 return completed; 135 } 136 137 const std::string* body() 138 { 139 return &response.body().str(); 140 } 141 142 std::string_view getHeaderValue(std::string_view key) const 143 { 144 return fields()[key]; 145 } 146 147 void keepAlive(bool k) 148 { 149 response.keep_alive(k); 150 } 151 152 bool keepAlive() const 153 { 154 return response.keep_alive(); 155 } 156 157 std::optional<uint64_t> size() 158 { 159 return response.body().payloadSize(); 160 } 161 162 void preparePayload() 163 { 164 // This code is a throw-free equivalent to 165 // beast::http::message::prepare_payload 166 std::optional<uint64_t> pSize = response.body().payloadSize(); 167 if (!pSize) 168 { 169 return; 170 } 171 using http::status; 172 using http::status_class; 173 using http::to_status_class; 174 bool is1XXReturn = to_status_class(result()) == 175 status_class::informational; 176 if (*pSize > 0 && (is1XXReturn || result() == status::no_content || 177 result() == status::not_modified)) 178 { 179 BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 180 "no-content or not_modified, which aren't " 181 "allowed to have a body", 182 logPtr(this)); 183 response.content_length(0); 184 return; 185 } 186 response.content_length(*pSize); 187 } 188 189 void clear() 190 { 191 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 192 response.clear(); 193 194 jsonValue = nullptr; 195 completed = false; 196 expectedHash = std::nullopt; 197 } 198 199 std::string computeEtag() const 200 { 201 // Only set etag if this request succeeded 202 if (result() != http::status::ok) 203 { 204 return ""; 205 } 206 // and the json response isn't empty 207 if (jsonValue.empty()) 208 { 209 return ""; 210 } 211 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 212 return "\"" + intToHexString(hashval, 8) + "\""; 213 } 214 215 void write(std::string&& bodyPart) 216 { 217 response.body().str() = std::move(bodyPart); 218 } 219 220 void end() 221 { 222 if (completed) 223 { 224 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 225 return; 226 } 227 completed = true; 228 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 229 if (completeRequestHandler) 230 { 231 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 232 completeRequestHandler(*this); 233 } 234 } 235 236 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 237 { 238 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 239 completeRequestHandler = std::move(handler); 240 241 // Now that we have a new completion handler attached, we're no longer 242 // complete 243 completed = false; 244 } 245 246 std::function<void(Response&)> releaseCompleteRequestHandler() 247 { 248 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 249 static_cast<bool>(completeRequestHandler)); 250 std::function<void(Response&)> ret = completeRequestHandler; 251 completeRequestHandler = nullptr; 252 completed = true; 253 return ret; 254 } 255 256 void setHashAndHandleNotModified() 257 { 258 // Can only hash if we have content that's valid 259 if (jsonValue.empty() || result() != http::status::ok) 260 { 261 return; 262 } 263 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 264 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 265 addHeader(http::field::etag, hexVal); 266 if (expectedHash && hexVal == *expectedHash) 267 { 268 jsonValue = nullptr; 269 result(http::status::not_modified); 270 } 271 } 272 273 void setExpectedHash(std::string_view hash) 274 { 275 expectedHash = hash; 276 } 277 278 bool openFile(const std::filesystem::path& path, 279 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 280 { 281 boost::beast::error_code ec; 282 response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 283 response.body().encodingType = enc; 284 if (ec) 285 { 286 BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str()); 287 return false; 288 } 289 return true; 290 } 291 292 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 293 { 294 boost::beast::error_code ec; 295 response.body().encodingType = enc; 296 response.body().setFd(fd, ec); 297 if (ec) 298 { 299 BMCWEB_LOG_ERROR("Failed to set fd"); 300 return false; 301 } 302 return true; 303 } 304 305 private: 306 std::optional<std::string> expectedHash; 307 bool completed = false; 308 std::function<void(Response&)> completeRequestHandler; 309 }; 310 } // namespace crow 311