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