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 response.body().clear(); 194 195 jsonValue = nullptr; 196 completed = false; 197 expectedHash = std::nullopt; 198 } 199 200 std::string computeEtag() const 201 { 202 // Only set etag if this request succeeded 203 if (result() != http::status::ok) 204 { 205 return ""; 206 } 207 // and the json response isn't empty 208 if (jsonValue.empty()) 209 { 210 return ""; 211 } 212 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 213 return "\"" + intToHexString(hashval, 8) + "\""; 214 } 215 216 void write(std::string&& bodyPart) 217 { 218 response.body().str() = std::move(bodyPart); 219 } 220 221 void end() 222 { 223 if (completed) 224 { 225 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 226 return; 227 } 228 completed = true; 229 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 230 if (completeRequestHandler) 231 { 232 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 233 completeRequestHandler(*this); 234 } 235 } 236 237 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 238 { 239 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 240 completeRequestHandler = std::move(handler); 241 242 // Now that we have a new completion handler attached, we're no longer 243 // complete 244 completed = false; 245 } 246 247 std::function<void(Response&)> releaseCompleteRequestHandler() 248 { 249 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 250 static_cast<bool>(completeRequestHandler)); 251 std::function<void(Response&)> ret = completeRequestHandler; 252 completeRequestHandler = nullptr; 253 completed = true; 254 return ret; 255 } 256 257 void setHashAndHandleNotModified() 258 { 259 // Can only hash if we have content that's valid 260 if (jsonValue.empty() || result() != http::status::ok) 261 { 262 return; 263 } 264 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 265 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 266 addHeader(http::field::etag, hexVal); 267 if (expectedHash && hexVal == *expectedHash) 268 { 269 jsonValue = nullptr; 270 result(http::status::not_modified); 271 } 272 } 273 274 void setExpectedHash(std::string_view hash) 275 { 276 expectedHash = hash; 277 } 278 279 bool openFile(const std::filesystem::path& path, 280 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 281 { 282 boost::beast::error_code ec; 283 response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 284 response.body().encodingType = enc; 285 if (ec) 286 { 287 BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str()); 288 return false; 289 } 290 return true; 291 } 292 293 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 294 { 295 boost::beast::error_code ec; 296 response.body().encodingType = enc; 297 response.body().setFd(fd, ec); 298 if (ec) 299 { 300 BMCWEB_LOG_ERROR("Failed to set fd"); 301 return false; 302 } 303 return true; 304 } 305 306 private: 307 std::optional<std::string> expectedHash; 308 bool completed = false; 309 std::function<void(Response&)> completeRequestHandler; 310 }; 311 } // namespace crow 312