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