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